package example.demo.route.plugins;


import example.demo.route.aop.RouteContext;
import example.demo.route.handler.SqlHandlerInterface;
import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.jdbc.ConnectionLogger;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;

/**
 * 如果是prepare阶段的话，则是invocation.proceed()之前就要修改sql语句
 * 如果是query 或者 update的话，修改sql语句也要修改statement对象的，要不然修改sql就无意义了
 * <p>
 * 这个自动就会加入到mybatis里面去，无需配置
 * <p>
 * 第一阶段【会场创建jdbc的statement对象】 第一阶段无需改动sql
 * mybatis的statement先是prepare
 * 第二阶段【执行sql语句】                第二阶段就需要改动sql了，同时也要去修改statement，不能使用第一阶段的statement对象了。
 * 之后要么是query【select】或者是【update，insert，delete】
 * 通常情况下，第二阶段是直接使用第一阶段的statement对象，而不会新创建statement对象，
 * 如果你在第二阶段去修改sql，之后不再新建statement对象的话，修该sql的意义就不存在了。
 */
//@Component
@Intercepts({
        //Statement prepare(Connection connection, Integer transactionTimeout)  再创建Statement之前做。原生jdbc的
        //select拦截prepare的阶段意义不大，主要是insert阶段
        @Signature(type = StatementHandler.class, method = "prepare",
                args = {Connection.class, Integer.class}),
        //<E> List<E> query(Statement statement, ResultHandler resultHandler)   
        @Signature(type = StatementHandler.class, method = "query",
                args = {Statement.class, ResultHandler.class}),
        @Signature(type = StatementHandler.class, method = "update",
                args = {Statement.class})})
public final class RoutePluginFinal implements Interceptor, InitializingBean {
    private List<SqlHandlerInterface> sqlHandlers;

    @Autowired
    DataSource dataSource;

    /**
     * 发现了SqlHandlerInterface都加了@Order就会按照指定的顺序加入，看来di这里会有一个排序过程。
     * 实例化的时候就不会安装@Order指定的顺序来搞了，你要验证的话，就把断点放在SqlHandlerParent的setDataSource()方法上
     */
    @Autowired
    public void setSqlHandlers(List<SqlHandlerInterface> sqlHandlers) {
        this.sqlHandlers = sqlHandlers;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        sqlHandlers.sort((bean1, bean2) -> {
            Order order1 = bean1.getClass().getAnnotation(Order.class);
            int value1 = order1.value();
            Order order2 = bean2.getClass().getAnnotation(Order.class);
            int value2 = order2.value();
            /*的到是 order越小就在list靠前*/
            return Integer.compare(value1, value2);
            /*的到是order越小就在list靠后*/
//            return Integer.compare(value2, value1);
        });
    }

    /**
     * 装饰器模式
     * RoutingStatementHandler会持有【PreparedStatementHandler，CallableStatementHandler，SimpleStatementHandler】
     * RoutingStatementHandler的 delegate 就是上面的其中三个其中的一个
     * 也就是RoutingStatementHandler委托给delegate去做事情。
     * <p>
     * BaseStatementHandler的三个子类【PreparedStatementHandler，CallableStatementHandler，SimpleStatementHandler】
     * 那三个子类都没有自己的属性，都是继承自BaseStatementHandler的
     */

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
//        if (invocation.getMethod().getName().equals("prepare")) {
//            return invocation.proceed();
//        }
        Integer[] tables = RouteContext.tables_thread_local.get();
        Integer[] tablesIdxs = RouteContext.tableIdxs_thread_local.get();

        Integer[] integer = tables == null ? tablesIdxs : tables;
        if (null != integer && integer.length != 0) {
            /**<? 避免内存遗漏*/
            RouteContext.removeAll();
            /*委托者*/
            RoutingStatementHandler routingStatementHandler = (RoutingStatementHandler) invocation.getTarget();
            Class<RoutingStatementHandler> routingStatementHandlerClass = RoutingStatementHandler.class;

            /*真正的操作人*/
            Field delegate = routingStatementHandlerClass.getDeclaredField("delegate");
            delegate.setAccessible(true);
            StatementHandler statementHandler = (StatementHandler) delegate.get(routingStatementHandler);

            BoundSql boundSql = statementHandler.getBoundSql();
            /**<? 获取到sql语句*/
            String sql = boundSql.getSql();
            /**<? 拿到sql属性*/
            Field sqlField = BoundSql.class.getDeclaredField("sql");
            sqlField.setAccessible(true);
            return handler(integer, sql, sqlField, invocation, boundSql, statementHandler);
        }

        /*如果是prepare，则内部是是创建statement对象，update，query的话则内部是使用prepare阶段创建的statement来去执行sql语句*/
        return invocation.proceed();
    }

    private Object handler(Integer[] integers, String sql, Field sqlField, Invocation invocation, BoundSql boundSql, StatementHandler statementHandler) throws Exception {
        for (SqlHandlerInterface sqlHandler : sqlHandlers) {
            if (sqlHandler.isSupport(sql)) {
                Object handler = sqlHandler.handler(integers, sql, invocation, boundSql, sqlField, statementHandler);
                if (null != handler) {
                    return handler;
                }
            }
        }
        return null;
    }

    @Override
    public Object plugin(Object target) {
        System.out.println();
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }


}
